Profile picture

[k8s] Deep Dive - Service(Cluster IP)

JaehyoJJAng2023년 04월 04일

▶︎ 개요

  • 각 서비스 타입별(ClusterIP,NodePort,LoadBalancer) 실습 및 서비스 특성 파악

image


▶︎ 사전 준비

실습환경 구성

  • git clone https://github.com/luksa/kubernetes-in-action-2nd-edition.git
  • kubectl apply -f kubernetes-in-action-2nd-edition/Chapter11/SETUP/quote

{% include codeHeader.html name="pv.yaml" %}

apiVersion: v1
kind: PersistentVolume
metadata:
  name: quiz-data
spec:
  capacity:
    storage: 20Gi
  accessModes:
    - ReadWriteOnce
    - ReadOnlyMany
  hostPath:
    path: /var/quiz-data
  storageClassName: ""

{% include codeHeader.html name="pvc.yaml" %}

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: quiz-data
spec:
  resources:
    requests:
      storage: 5Gi
  accessModes:
  - ReadWriteOnce
  storageClassName: ""
  volumeName: quiz-data

{% include codeHeader.html name="pod.yaml" %}

apiVersion: v1
kind: Pod
metadata:
  name: quiz
  labels:
    app: quiz
    rel: stable
spec:
  volumes:
  - name: initdb
    emptyDir: {}
  - name: quiz-data
    persistentVolumeClaim:
      claimName: quiz-data
  initContainers:
  - name: installer
    image: luksa/quiz-initdb-script-installer:0.1
    imagePullPolicy: Always
    volumeMounts:
    - name: initdb
      mountPath: /initdb.d
  containers:
  - name: quiz-api
    image: luksa/quiz-api:0.1
    imagePullPolicy: Always
    ports:
    - name: http
      containerPort: 8080
  - name: mongo
    image: mongo:4.4.6
    volumeMounts:
    - name: quiz-data
      mountPath: /data/db
    - name: initdb
      mountPath: /docker-entrypoint-initdb.d/
      readOnly: true

▶︎ ClusterIP

(1) ClusterIP는 서비스 리소스의 가장 기본이 되는 타입이다. 별다른 타입 지정을 하지 않으면 기본적으로 ClusterIP로 설정된다.
(2) 외부에서는 접근할 수 없으며 쿠버네티스 내부에서만 Pod에서 접근할 때 사용한다.


‣ 사용목적

외부와 통신이 불가능한데 ClusterIP를 왜 사용하는걸까? 이유는 크게 2가지로 나뉜다.

1. 네트워크 보안 및 관리 목적
일반적으로 보안상 한 두개의 외부 서비스 외에 직접적으로 트래픽을 전달받는 경우는 드물다. 열린 서비스 끝점으로부터 트래픽을 전달받아 내부 파드와 소통하는데 이용한다.


2. 복잡한 네트워킹 수행 가능
쿠버네티스 서비스를 정의하는 메니페스트 파일을 통해 보다 정교하게 네트워킹을 정의하고 수행할 수 있다.


‣ 서비스 생성

quote Pod 대상 manifest 파일을 생성하여 서비스를 생성해보자.

apiVersion: v1
kind: Service
metadata:
  name: quote
spec:
  type: ClusterIP
  selector:
    app: quote
  ports:
  - name: http
    port: 80
    targetPort: 80
    protocol: TCP

위와 같이 yaml 파일을 작성하고 kubectl apply 명령어로 서비스를 생성하자
image
① quote라는 서비스 이름으로 정의하며 포트 80의 연결을 허용한다.
② 각 연결을 포트 80으로 레이블 app=quote 와 일치하는 Pod로 포워딩한다.


Quiz Pod에 대해서 명령형 커맨드(Imperative Command)를 통해 서비스를 생성해 보자.

$ kubectl expose pod quiz --name quiz

$ kubectl describe svc/quiz
Name:              quiz
Namespace:         default
Labels:            app=quiz
                   rel=stable
Annotations:       <none>
Selector:          app=quiz,rel=stable
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                10.106.37.233
IPs:               10.106.37.233
Port:              <unset>  8080/TCP
TargetPort:        8080/TCP
Endpoints:         20.109.131.19:8080
Session Affinity:  None
Events:            <none>

여기서 quiz 파드에 적용된 레이블 app=quiz, rel=stable이 적용된 것을 확인할 수 있다. 그러나, quiz라는 서비스가 stable 레이블이 적용된 것이 아닌 조금 더 광범위적으로 적용하는 상황을 고려하여 app=quiz 레이블만 적용할 수 있도록 정책을 수정해보자.

# 레이블 설렉터를 app=quiz로 설정하도록 변경
$ kubectl set selector service quiz app=quiz
service/quiz selector updated

# 정책 반영 확인
$ kubectl get svc quiz -o wide
NAME   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE   SELECTOR
quiz   ClusterIP   10.106.37.233   <none>        8080/TCP   98s   app=quiz

추가적으로, 이미 생성한 서비스에서 포트 수정도 가능하다.
kubectl edit 기능을 활용하여 8080:8080 을 80:8080으로 수정해보자.

$ kubectl edit svc quiz

‣ 서비스 확인

$ kubectl get svc -o wide
NAME    TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE     SELECTOR
quiz    ClusterIP   10.104.206.158   <none>        80/TCP    4m51s   app=quiz
quote   ClusterIP   10.97.190.49     <none>        80/TCP    5m42s   app=quote

‣ Service 접근

이제 quote-001 파드로부터 일전에 생성한 quote, quiz 서비스에 대하여 접근이 가능한지 시도해보자.

나의 경우, quiz 서비스 ClusterIP는 10.104.206.158 quote 서비스 ClusterIP는 10.97.190.49 이다.

$ kubectl get svc -o wide
NAME    TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE     SELECTOR
quiz    ClusterIP   10.104.206.158   <none>        80/TCP    4m51s   app=quiz
quote   ClusterIP   10.97.190.49     <none>        80/TCP    5m42s   app=quote

우선, quote-001 내부에서 Shell을 실행시켜보자. 그리고, quote-001로부터 quiz, quote 서비스에 접속이 되는지 확인해보자.


파드 내부 접속

kubectl exec -it quote-001 -c nginx -- sh
/ #

# quote-001(Pod) → quiz(Service) 접속
/ # curl http://10.104.206.158
This is the quiz service running in pod quiz

# quote-001(Pod) → quote(Service) 접속
/ # curl http://10.97.190.49
This is the quote service running in pod quote-003 on node k8s-w3

파드가 여러개인 경우, 서비스가 로드밸런서와 같이 다른 Pod들에 여러번 접근하는 것을 확인할 수 있다.

/ # while true; do curl http://10.97.190.49; done
This is the quote service running in pod quote-001 on node k8s-w2
This is the quote service running in pod quote-002 on node k8s-w1
This is the quote service running in pod quote-001 on node k8s-w2
This is the quote service running in pod quote-003 on node k8s-w3
This is the quote service running in pod quote-003 on node k8s-w3
This is the quote service running in pod quote-canary on node k8s-w3
This is the quote service running in pod quote-002 on node k8s-w1

💡 기본적으로 서비스는 트래픽을 랜덤하게 파드로 분산시킨다. 만약, 특정 클라이언트와 특정 Pod를 지속적으로 연결이 되게 하려면 spec.sessionAffinity를 통해 구현할 수 있다.

SessionAffinity 타입은 None, Client IP 2가지만 지원한다. (None은 랜덤하게 파드로 로드밸런싱 한다.)


Loading script...